home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Browsers, Managers & Extensions / Mozilla Weave 0.2.7 / latest-weave.xpi / modules / dav.js < prev    next >
Text File  |  2008-08-08  |  15KB  |  494 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Bookmarks Sync.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Dan Mills <thunder@mozilla.com>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const EXPORTED_SYMBOLS = ['DAV', 'DAVCollection'];
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41. const Cr = Components.results;
  42. const Cu = Components.utils;
  43.  
  44. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  45. Cu.import("resource://weave/log4moz.js");
  46. Cu.import("resource://weave/constants.js");
  47. Cu.import("resource://weave/util.js");
  48. Cu.import("resource://weave/identity.js");
  49. Cu.import("resource://weave/async.js");
  50.  
  51. Function.prototype.async = Async.sugar;
  52.  
  53. Utils.lazy(this, 'DAV', DAVCollection);
  54.  
  55. let DAVLocks = {default: null};
  56.  
  57. /*
  58.  * DAV object
  59.  * Abstracts the raw DAV commands
  60.  */
  61.  
  62. function DAVCollection(baseURL, defaultPrefix) {
  63.   this.baseURL = baseURL;
  64.   this.defaultPrefix = defaultPrefix;
  65.   this._identity = 'DAV:default';
  66.   this._log = Log4Moz.Service.getLogger("Service.DAV");
  67.   this._log.level =
  68.     Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.dav")];
  69. }
  70. DAVCollection.prototype = {
  71.  
  72.   __dp: null,
  73.   get _dp() {
  74.     if (!this.__dp)
  75.       this.__dp = Cc["@mozilla.org/xmlextras/domparser;1"].
  76.         createInstance(Ci.nsIDOMParser);
  77.     return this.__dp;
  78.   },
  79.  
  80.   get identity() { return this._identity; },
  81.   set identity(value) { this._identity = value; },
  82.  
  83.   get baseURL() {
  84.     return this._baseURL;
  85.   },
  86.   set baseURL(value) {
  87.     if (value && value[value.length-1] != '/')
  88.       value = value + '/';
  89.     this._baseURL = value;
  90.   },
  91.  
  92.   get defaultPrefix() {
  93.     return this._defaultPrefix;
  94.   },
  95.   set defaultPrefix(value) {
  96.     if (value && value[value.length-1] != '/')
  97.       value = value + '/';
  98.     if (value && value[0] == '/')
  99.       value = value.slice(1);
  100.     if (!value)
  101.       value = '';
  102.     this._defaultPrefix = value;
  103.   },
  104.  
  105.   get locked() {
  106.     return !this._allowLock || (DAVLocks['default'] &&
  107.                                 DAVLocks['default'].token);
  108.   },
  109.  
  110.   _allowLock: true,
  111.   get allowLock() this._allowLock,
  112.   set allowLock(value) {
  113.     this._allowLock = value;
  114.   },
  115.  
  116.   _makeRequest: function DC__makeRequest(op, path, headers, data) {
  117.     let self = yield;
  118.     let ret;
  119.  
  120.     this._log.debug(op + " request for " + (path? path : 'root folder'));
  121.  
  122.     if (!path || path[0] != '/')
  123.       path = this._defaultPrefix + path; // if relative: prepend default prefix
  124.     else
  125.       path = path.slice(1); // if absolute: remove leading slash
  126.     // path at this point should have no leading slash.
  127.  
  128.     if (this._lastProgress)
  129.       throw "Request already in progress";
  130.     else
  131.       this._lastProgress = Date.now();
  132.  
  133.     let xhrCb = self.cb;
  134.     let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  135.       createInstance(Ci.nsIXMLHttpRequest);
  136.  
  137.     // check for stalled connections
  138.     let listener = new Utils.EventListener(this._timeoutCb(request, xhrCb));
  139.     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  140.     timer.initWithCallback(listener, CONNECTION_TIMEOUT,
  141.                            timer.TYPE_REPEATING_SLACK);
  142.  
  143.     request.onload = xhrCb;
  144.     request.onerror = xhrCb;
  145.     request.onprogress =Utils.bind2(this, this._onProgress);
  146.     request.mozBackgroundRequest = true;
  147.     request.open(op, this._baseURL + path, true);
  148.  
  149.  
  150.     // Force cache validation
  151.     let channel = request.channel;
  152.     channel = channel.QueryInterface(Ci.nsIRequest);
  153.     let loadFlags = channel.loadFlags;
  154.     loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS;
  155.     channel.loadFlags = loadFlags;
  156.  
  157.     let key;
  158.     for (key in headers) {
  159.       if (key == 'Authorization')
  160.         this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
  161.       else
  162.         this._log.trace("HTTP Header " + key + ": " + headers[key]);
  163.       request.setRequestHeader(key, headers[key]);
  164.     }
  165.  
  166.     let event = yield request.send(data);
  167.  
  168.     timer.cancel();
  169.     this._lastProgress = null;
  170.     self.done(event.target);
  171.   },
  172.  
  173.   _onProgress: function DC__onProgress(event) {
  174.     this._lastProgress = Date.now();
  175.   },
  176.  
  177.   _timeoutCb: function DC__timeoutCb(request, callback) {
  178.     return function() {
  179.       if (Date.now() - this._lastProgress > CONNECTION_TIMEOUT) {
  180.         this._log.warn("Connection timed out");
  181.         request.abort();
  182.         callback({target:{status:-1}});
  183.       }
  184.     };
  185.   },
  186.  
  187.   get _defaultHeaders() {
  188.     let h = {'Content-type': 'text/plain'},
  189.       id = ID.get(this.identity),
  190.       lock = DAVLocks['default'];
  191.     if (id)
  192.       h['Authorization'] = 'Basic ' + btoa(id.username + ":" + id.password);
  193.     if (lock)
  194.       h['If'] = "<" + lock.URL + "> (<" + lock.token + ">)";
  195.     return h;
  196.   },
  197.  
  198.   // mkdir -p
  199.   _mkcol: function DC__mkcol(path) {
  200.     let self = yield;
  201.     let ok = true;
  202.  
  203.     try {
  204.       let components = path.split('/');
  205.       let path2 = '';
  206.  
  207.       for (let i = 0; i < components.length; i++) {
  208.  
  209.         // trailing slashes will cause an empty path component at the end
  210.         if (components[i] == '')
  211.           continue;
  212.  
  213.         path2 = path2 + components[i];
  214.  
  215.         // check if it exists first
  216.         this._makeRequest.async(this, self.cb, "GET", path2 + "/", this._defaultHeaders);
  217.         let ret = yield;
  218.         if (ret.status != 404) {
  219.           this._log.trace("Skipping creation of path " + path2 +
  220.                   " (got status " + ret.status + ")");
  221.         } else {
  222.           this._log.debug("Creating path: " + path2);
  223.           this._makeRequest.async(this, self.cb, "MKCOL", path2,
  224.                       this._defaultHeaders);
  225.           ret = yield;
  226.  
  227.           if (ret.status != 201) {
  228.             this._log.debug(ret.responseText);
  229.             throw 'request failed: ' + ret.status;
  230.           }
  231.         }
  232.  
  233.         // add slash *after* the request, trailing slashes cause a 412!
  234.         path2 = path2 + "/";
  235.       }
  236.  
  237.     } catch (e) {
  238.       this._log.error("Could not create directory on server");
  239.       this._log.error("Exception caught: " + (e.message? e.message : e) +
  240.                       " - " + (e.location? e.location : ""));
  241.       ok = false;
  242.     }
  243.  
  244.     self.done(ok);
  245.   },
  246.  
  247.   GET: function DC_GET(path, onComplete) {
  248.     return this._makeRequest.async(this, onComplete, "GET", path,
  249.                                    this._defaultHeaders);
  250.   },
  251.  
  252.   POST: function DC_POST(path, data, onComplete) {
  253.     return this._makeRequest.async(this, onComplete, "POST", path,
  254.                                    this._defaultHeaders, data);
  255.   },
  256.   
  257.   formPost: function DC_formPOST(path, data, onComplete) {
  258.     let headers = {'Content-type': 'application/x-www-form-urlencoded'};
  259.     headers.__proto__ = this._defaultHeaders;
  260.     
  261.     return this._makeRequest.async(this, onComplete, "POST", path,
  262.                                    headers, data);
  263.   },
  264.  
  265.   PUT: function DC_PUT(path, data, onComplete) {
  266.     return this._makeRequest.async(this, onComplete, "PUT", path,
  267.                                    this._defaultHeaders, data);
  268.   },
  269.  
  270.   DELETE: function DC_DELETE(path, onComplete) {
  271.     return this._makeRequest.async(this, onComplete, "DELETE", path,
  272.                                    this._defaultHeaders);
  273.   },
  274.  
  275.   MKCOL: function DC_MKCOL(path, onComplete) {
  276.     return this._mkcol.async(this, onComplete, path);
  277.   },
  278.  
  279.   PROPFIND: function DC_PROPFIND(path, data, onComplete) {
  280.     let headers = {'Content-type': 'text/xml; charset="utf-8"',
  281.                    'Depth': '0'};
  282.     headers.__proto__ = this._defaultHeaders;
  283.     return this._makeRequest.async(this, onComplete, "PROPFIND", path,
  284.                                    headers, data);
  285.   },
  286.  
  287.   LOCK: function DC_LOCK(path, data, onComplete) {
  288.     let headers = {'Content-type': 'text/xml; charset="utf-8"',
  289.                    'Depth': 'infinity',
  290.                    'Timeout': 'Second-600'};
  291.     headers.__proto__ = this._defaultHeaders;
  292.     return this._makeRequest.async(this, onComplete, "LOCK", path, headers, data);
  293.   },
  294.  
  295.   UNLOCK: function DC_UNLOCK(path, onComplete) {
  296.     let headers = {'Lock-Token': '<' + DAVLocks['default'].token + '>'};
  297.     headers.__proto__ = this._defaultHeaders;
  298.     return this._makeRequest.async(this, onComplete, "UNLOCK", path, headers);
  299.   },
  300.  
  301.   // Get all files
  302.   listFiles: function DC_listFiles(path) {
  303.     let self = yield;
  304.  
  305.     if (!path)
  306.       path = "";
  307.  
  308.     let headers = {'Content-type': 'text/xml; charset="utf-8"',
  309.                    'Depth': '1'};
  310.     headers.__proto__ = this._defaultHeaders;
  311.  
  312.     this._makeRequest.async(this, self.cb, "PROPFIND", path, headers,
  313.                            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
  314.                             "<D:propfind xmlns:D='DAV:'><D:prop/></D:propfind>");
  315.     let resp = yield;
  316.     Utils.ensureStatus(resp.status, "propfind failed");
  317.  
  318.     let ret = [];
  319.     try {
  320.       let elts = Utils.xpath(resp.responseXML, '//D:href');
  321.       // FIXME: shouldn't depend on the first one being the root
  322.       let root = elts.iterateNext();
  323.       root = root.textContent;
  324.       let elt;
  325.       while (elt = elts.iterateNext())
  326.         ret.push(elt.textContent.replace(root, ''));
  327.     } catch (e) {}
  328.  
  329.     self.done(ret);
  330.   },
  331.  
  332.   // Login / Logout
  333.  
  334.   checkLogin: function DC_checkLogin(username, password) {
  335.     let self = yield;
  336.  
  337.     this._log.debug("checkLogin called for user " + username);
  338.  
  339.     let headers = {
  340.                     'Content-type'  : 'text/plain',
  341.                     'Authorization' : 'Basic ' + btoa(username + ":" + password)
  342.                   };
  343.     let lock = DAVLocks['default'];
  344.     if (lock)
  345.       headers['If'] = "<" + lock.URL + "> (<" + lock.token + ">)";
  346.  
  347.     // Make a call to make sure it's working
  348.     this._makeRequest.async(this, self.cb, "GET", "", headers);
  349.     let resp = yield;
  350.  
  351.     this._log.debug("checkLogin got response status " + resp.status);
  352.     self.done(resp.status);
  353.   },
  354.  
  355.   // Locking
  356.  
  357.   _getActiveLock: function DC__getActiveLock() {
  358.     let self = yield;
  359.     let ret = null;
  360.  
  361.     this._log.debug("Getting active lock token");
  362.     this.PROPFIND("lock",
  363.                   "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
  364.                   "<D:propfind xmlns:D='DAV:'>" +
  365.                   "  <D:prop><D:lockdiscovery/></D:prop>" +
  366.                   "</D:propfind>", self.cb);
  367.     let resp = yield;
  368.  
  369.     if (resp.status < 200 || resp.status >= 300) {
  370.       self.done(false);
  371.       yield;
  372.     }
  373.  
  374.     let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
  375.     let token = tokens.iterateNext();
  376.     if (token)
  377.       ret = token.textContent;
  378.  
  379.     if (ret)
  380.       this._log.trace("Found an active lock token");
  381.     else
  382.       this._log.trace("No active lock token found");
  383.     self.done({URL: this._baseURL, token: ret});
  384.   },
  385.  
  386.   lock: function DC_lock() {
  387.     let self = yield;
  388.     let resp;
  389.  
  390.     try {
  391.       this._log.trace("Acquiring lock");
  392.  
  393.       if (this.locked) {
  394.         this._log.debug("Lock called, but we are already locked");
  395.         return;
  396.       }
  397.       this._allowLock = false;
  398.  
  399.       resp = yield this.LOCK("lock",
  400.                              "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
  401.                              "<D:lockinfo xmlns:D=\"DAV:\">\n" +
  402.                              "  <D:locktype><D:write/></D:locktype>\n" +
  403.                              "  <D:lockscope><D:exclusive/></D:lockscope>\n" +
  404.                              "</D:lockinfo>", self.cb);
  405.       if (!Utils.checkStatus(resp.status))
  406.         return;
  407.  
  408.       let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
  409.       let token = tokens.iterateNext();
  410.       if (token) {
  411.         DAVLocks['default'] = {
  412.           URL: this._baseURL,
  413.           token: token.textContent
  414.         };
  415.       }
  416.  
  417.       if (DAVLocks['default']) {
  418.         this._log.trace("Lock acquired");
  419.         self.done(DAVLocks['default']);
  420.       }
  421.  
  422.     } catch (e) {
  423.       this._log.error("Could not acquire lock");
  424.       if (resp.responseText)
  425.         this._log.error("Server response to LOCK:\n" + resp.responseText);
  426.       throw e;
  427.  
  428.     } finally {
  429.       this._allowLock = true;
  430.     }
  431.   },
  432.  
  433.   unlock: function DC_unlock() {
  434.     let self = yield;
  435.  
  436.     this._log.trace("Releasing lock");
  437.  
  438.     if (!this.locked) {
  439.       this._log.debug("Unlock called, but we don't hold a token right now");
  440.       self.done(true);
  441.       return;
  442.     }
  443.  
  444.     try {
  445.       let resp = yield this.UNLOCK("lock", self.cb);
  446.  
  447.       if (Utils.checkStatus(resp.status)) {
  448.         this._log.trace("Lock released");
  449.         self.done(true);
  450.       } else {
  451.         this._log.trace("Failed to release lock");
  452.         self.done(false);
  453.       }
  454.  
  455.     } catch (e) {
  456.       throw e;
  457.  
  458.     } finally {
  459.       // Do this unconditionally, since code that calls unlock() doesn't
  460.       // really have much of an option if unlock fails.  The only thing
  461.       // to do is wait for it to time out (and hope it didn't really
  462.       // fail)
  463.       if (DAVLocks['default'])
  464.         delete DAVLocks['default'];
  465.     }
  466.   },
  467.  
  468.   forceUnlock: function DC_forceUnlock() {
  469.     let self = yield;
  470.     let unlocked = true;
  471.  
  472.     this._log.debug("Forcibly releasing any server locks");
  473.  
  474.     this._getActiveLock.async(this, self.cb);
  475.     DAVLocks['default'] = yield;
  476.  
  477.     if (!DAVLocks['default']) {
  478.       this._log.debug("No server lock found");
  479.       self.done(true);
  480.       yield;
  481.     }
  482.  
  483.     this._log.trace("Server lock found, unlocking");
  484.     this.unlock.async(this, self.cb);
  485.     unlocked = yield;
  486.  
  487.     if (unlocked)
  488.       this._log.trace("Lock released");
  489.     else
  490.       this._log.trace("No lock released");
  491.     self.done(unlocked);
  492.   }
  493. };
  494.